use rustler::{Encoder, Env, Error, NifResult, NifTuple, ResourceArc, Term};
use recastnavigation_rs::demo::*;
use recastnavigation_rs::detour::*;
use std::sync::Mutex;

// docs: https://docs.rs/rustler/latest/rustler/index.html

const POLY_PICK_EXT: [f32; 3] = [2.0, 4.0, 2.0];
const MAX_POLYS: usize = 256;

mod atoms {
    rustler::atoms! {
        ok,
        open,
        close,
        element,
        empty,

        timestamp,
        int,
        float,
        string,
        list,

        done,
    }
}


#[derive(NifTuple)]
pub struct PosTuple {
    x: f32,
    y: f32,
    z: f32
}

/// 定义一个资源结构体来存储 `navmesh` 数据
pub struct NavmeshResource {
    nav_mesh: Mutex<DtNavMesh>,
}
unsafe impl Send for NavmeshResource {}
unsafe impl Sync for NavmeshResource {}

impl NavmeshResource {
    // 新增线程安全访问方法
    pub fn with_nav_mesh<F, R>(&self, f: F) -> R
    where
        F: FnOnce(&DtNavMesh) -> R,
    {
        let guard = self.nav_mesh.lock().unwrap();
        f(&guard)
    }
}

#[rustler::nif]
fn path_find<'a>(
    env: Env<'a>,
    resource: ResourceArc<NavmeshResource>,
    start: PosTuple,
    end: PosTuple,
) -> Term<'a> {
    let start_pos = [start.x, start.y, start.z];
    let end_pos = [end.x, end.y, end.z];

    // 使用线程安全方式访问 nav_mesh
    let result = resource.with_nav_mesh(|nav_mesh| {
        path_find_straight(nav_mesh, start_pos, end_pos)
    });

    path_to_erlang_tuple_list(result.straight_path, env)
}


fn path_to_erlang_tuple_list<'a>(path: Vec<[f32; 3]>, env: Env<'a>) -> Term<'a> {
    path.into_iter()
        .map(|pos| PosTuple { x: pos[0], y: pos[1], z: pos[2] })
        .collect::<Vec<_>>()
        .encode(env)
}


#[rustler::nif]
fn load_navmesh(navmesh_file: String) -> NifResult<ResourceArc<NavmeshResource>> {
    load_nav_mesh(&navmesh_file)
        .map(|nav_mesh| {
            ResourceArc::new(NavmeshResource {
                nav_mesh: Mutex::new(nav_mesh),
            })
        })
        .map_err(|e| Error::Term(Box::new(format!("Failed to load navmesh: {:?}", e))))
}

pub fn load(env: Env, _term: Term) -> bool
{
    let _ = rustler::resource!(NavmeshResource, env);
    true
}

rustler::init!("navmesh_nif", load = load);


// copy from recastnavigation-rs/tests/query.rs
#[derive(Default)]
struct PathFindStraightData {
    polys: Vec<DtPolyRef>,
    straight_path: Vec<[f32; 3]>,
    straight_path_flags: Vec<u8>,
    straight_path_refs: Vec<DtPolyRef>,
}

fn path_find_straight(nav_mesh: &DtNavMesh, start: [f32; 3], end: [f32; 3]) -> PathFindStraightData {
    let query = DtNavMeshQuery::with_mesh(nav_mesh, 2048)
            .expect("Failed to create navmesh query");
    let filter = DtQueryFilter::default();

    let mut data = PathFindStraightData {
        ..PathFindStraightData::default()
    };
    let (start_ref, _) = query.find_nearest_poly_1(&start, &POLY_PICK_EXT, &filter).unwrap();
    let (end_ref, _) = query.find_nearest_poly_1(&end, &POLY_PICK_EXT, &filter).unwrap();

    let mut polys = [DtPolyRef::default(); MAX_POLYS];
    let npolys = query
        .find_path(start_ref, end_ref, &start, &end, &filter, &mut polys)
        .unwrap();
    data.polys = polys[0..npolys].to_vec();

    let mut real_end = end;
    if polys[npolys - 1] != end_ref {
        (real_end, _) = query.closest_point_on_poly(polys[npolys - 1], &end).unwrap();
    }

    let mut straight_path = vec![[0.0; 3]; MAX_POLYS];
    let mut straight_path_flags = vec![0; MAX_POLYS];
    let mut straight_path_refs = vec![DtPolyRef::default(); MAX_POLYS];
    let straight_size = query
        .find_straight_path(
            &start,
            &real_end,
            &polys[0..npolys],
            &mut straight_path,
            Some(&mut straight_path_flags),
            Some(&mut straight_path_refs),
            0,
        )
        .unwrap();
    data.straight_path = straight_path[0..straight_size].to_vec();
    data.straight_path_flags = straight_path_flags[0..straight_size].to_vec();
    data.straight_path_refs = straight_path_refs[0..straight_size].to_vec();
    data
}
